''' -------------------------------------------------------------------------------
 Filename: bioimped_serial.py
 Author:   Chris Chester (christopher.chester@pg.canterbury.ac.nz) adapted from work
                            done by Ben Han <uha10@uclive.ac.nz> and Andrew Nicholas 
 Date:     27 July 2012

	Comment: Interface with Swallowing Rehab Instrument
           Expects a 12bit digit in the form:
           <sync byte><low byte><high byte>


 Serial data acquisition module used by biofeedback application
 Adapted from 'miniterm.py' by:
   (C)2002-2009 Chris Liechti <cliechti@gmx.net>

 -------------------------------------------------------------------------------'''

import sys, threading, time, traceback
import serial
import glob

#===============================================================================
# CLASS: Console
#    A class used to extract and organise data received from the bioimpedance device
#===============================================================================
class Console():
    
    BAUDRATE = 115200    # data rate from the device
    TIMEOUT = 1         # number of seconds to wait on port operations before timing out

    def __init__(self, update_data, port, s_evt):
        """ Inputs - update_data, port 
            update_data - callable object which accepts an integer value as an argument.
            port - string representing the port to connect to
            s_evt - event that is set when the reader loop breaks
        """
        self.update_data = update_data
        self.port = port
        self.serial_loop_closed = s_evt

        # Attempt at connecting to the bioimpedance serial device
        try:
            for port in self.scan():
                self.port = port

            #self.port = "/dev/cu.usbserial-FTULE19P"
            self.ser_port = serial.Serial(self.port, self.BAUDRATE, timeout=self.TIMEOUT)
            sys.stdout.write('>> Serial port %r opened.\n' % self.port)
            self.alive = True

            # Start serial->console thread
            self.receiver_thread = threading.Thread(target=self.reader, name='SerialThread')
            self.receiver_thread.start()
        except serial.SerialException, e:
            self.stop()
            raise
    def scan(self):
        ''' Used to find the corresponding serial port in a mac'''
        return glob.glob('/dev/cu.usb*')

    def stop(self):
        ''' Stops the while loop, close serial port and wait for current iteration to finish'''
        
        self.alive = False
        self.serial_loop_closed.wait()  # wait until the while loop exits
        
        # Checks to see if actually connected to a serial port
        if self.ser_port:
            try:
                self.ser_port.flush()
            except:
                sys.stderr.write("Error flushing the serial buffer.\n")
                raise
                
            try:
                self.ser_port.close()   # closes the port immediately, ensure read() command is not blocking first
                sys.stdout.write('>> Serial port %r closed.\n' % self.port)
            except:
                sys.stderr.write('\nCould not close serial connection.\n')
                raise
            
    def getBaudrate(self):
        '''Returns the baudrate of the connected serial'''
        self.ser_port.get
        return self.ser_port.getBaudrate()

            
    def flush(self):
        '''Flush out the serial buffer'''
        self.ser_port.flush()
        
    def reader(self):
        ''' The main loop which reads the serial port then passes data to the plotting application.
                             The binary data read from the serial port is decoded by this function.'''
        # Defining a hexidecimal to binary conversion
        hex2bin = { '0':'0000', '1':'0001', '2':'0010', '3':'0011', '4':'0100', '5':'0101', '6':'0110', '7':'0111',
                    '8':'1000', '9':'1001', 'a':'1010', 'b':'1011', 'c':'1100', 'd':'1101', 'e':'1110', 'f':'1111'}

        # Initialise variable values
        position = 0 # position in code block: sync byte, high byte ch1, low byte ch1, high byte ch2 and low byte ch2
        highByte = 0
        lowByte = 0
        emgLevel1 = 0 # The impedance value received from ch 1
        emglevel2 = 0 # The impedance value received from ch 2
        
        SYNC_BYTE = 'ff'
        LOW_BYTE_CH1 = 1
        HIGH_BYTE_CH1 = 2
        LOW_BYTE_CH2 = 3
        HIGH_BYTE_CH2 = 4
        
        BYTE_SHIFT = 8

        sys.stderr.write('Receiver thread started.\n')


        # Clear the serial_loop_closed event flag to indicate that the serial reader loop 
        # is now running and update_data() will be called periodically until the loop ends.
        self.serial_loop_closed.clear()
        
        while self.alive:
        # BEGIN while loop
            try:
                data = self.ser_port.read(self.TIMEOUT)    # this command will block for <timeout> secs then pass
            except:
                self.alive = False      # close serial channel in event of any error
                if self.ser_port.isOpen():
                    self.ser_port.close()
                sys.stderr.write("Error reading from the serial channel. Port is now CLOSED.\n")
                sys.stderr.write("Please ensure the device connected to the serial port before\n")
                sys.stderr.write("attempting to reconnect.\n")
                break   # end the while loop. This will set the serial_loop_closed event flag to set as it exits.
                
            # escape everything (hexdump)
            # Iterated through all of the characters provided in the data sample
                        
            for character in data:
                ch = character.encode('hex')
                                        
                if ((position == 0) & (ch == SYNC_BYTE)) :    # Check for sync byte
                    emgLevel1 = 0
                    emgLevel2 = 0;
                    position = LOW_BYTE_CH1


                elif position == LOW_BYTE_CH1:
                    lowByte = ord(character)
                    position = HIGH_BYTE_CH1


                elif position == HIGH_BYTE_CH1:
                    highByte = ord(character)
                    
                    # Combining both high and low bytes to make a 16bit value
                    emgLevel1 = highByte << BYTE_SHIFT
                    emgLevel1 += lowByte
                    
                    # Extracting data for the second channel
                    position = LOW_BYTE_CH2

                elif position == LOW_BYTE_CH2:
                    lowByte = ord(character)
                    position = HIGH_BYTE_CH2


                elif position == HIGH_BYTE_CH2:
                    highByte = ord(character)

                    # Combining both high and low bytes to make a 16bit value
                    emgLevel2 = highByte << BYTE_SHIFT
                    emgLevel2 += lowByte

                    if self.alive:
                        try:
                            self.update_data(emgLevel1, emgLevel2)      # update the data arrays
                            print "Values"
                            print emgLevel1
                            print emgLevel2
                            
                        except:
                            print traceback.format_exc()
                    position = 0
                else:
                    break
            try:
                sys.stdout.flush()
            except:
                sys.stderr.write("Exception in Console.reader: %s\n%s\n" % (sys.exc_info()[0], sys.exc_info()[1]))
        # END while loop
        
        # Set the serial_loop_closed event flag to indicate that the serial reader loop 
        # is now finished and update_data() will no longer be called.
        self.serial_loop_closed.set()
        sys.stdout.write('Receiver thread ended.\n')
        
# End of myopace_serial.py
